use byte_unit::Byte;
use log::{debug, error, info, trace, warn};
use serde::Deserialize;
use serde_json::{json, Value as JsonValue};
use serde_repr::Deserialize_repr;
use tauri::AppHandle;
use tauri::{
  plugin::{Plugin, Result as PluginResult},
  Invoke, Runtime,
};

use std::path::{Path, PathBuf};

use chrono::{NaiveDate, NaiveDateTime};
use futures::future::BoxFuture;
use futures::future::Future;
use once_cell::sync::Lazy;
use sea_query::{ColumnDef, Expr, Func, Iden, Order, Query, SqliteQueryBuilder, Table};
use sqlx::{
  error::BoxDynError,
  migrate::{
    MigrateDatabase, Migration as SqlxMigration, MigrationSource, MigrationType, Migrator,
  },
  Column, Pool, Row, TypeInfo,
};
use tokio::sync::Mutex;

use std::{collections::HashMap, sync::Arc};

sea_query::sea_query_driver_sqlite!();
use sea_query_driver_sqlite::{bind_query, bind_query_as};
use uuid::Uuid;

pub use log::LevelFilter;

type Db = sqlx::sqlite::Sqlite;

fn db_instances() -> &'static Arc<Mutex<HashMap<String, Pool<Db>>>> {
  static DBS: Lazy<Arc<Mutex<HashMap<String, Pool<Db>>>>> = Lazy::new(Default::default);
  &DBS
}

#[derive(Debug, Default, Deserialize)]
#[serde(rename_all = "camelCase")]
struct LogConfiguration {
  max_file_size: Option<String>,
}

/// The available verbosity levels of the logger.
#[derive(Deserialize_repr, Debug)]
#[repr(u16)]
pub enum LogLevel {
  Trace = 1,
  Debug,
  Info,
  Warn,
  Error,
}

pub enum RotationStrategy {
  KeepOne,
  KeepAll,
}

const DEFAULT_MAX_FILE_SIZE: u128 = 40000;
const DB_URL: &'static str ="ebmaster.db";

fn get_log_file_path<P: AsRef<Path>>(
  config: &LogConfiguration,
  dir: P,
  rotation_strategy: &RotationStrategy,
) -> PluginResult<PathBuf> {
  let path = dir.as_ref().join("sqlite.log");

  Ok(path)
}

#[tauri::command]
fn hello(message: String) {
  info!("tauri-plugin-sqlite.hello(message): {}", message)
}

pub fn logyyy(level: LogLevel, message: String) {
  match level {
    LogLevel::Trace => trace!("yyy{}", message),
    LogLevel::Debug => debug!("yyy{}", message),
    LogLevel::Info => info!("yyy{}", message),
    LogLevel::Warn => warn!("yyy{}", message),
    LogLevel::Error => error!("yyy{}", message),
  }
}

pub fn logzzz(level: LogLevel, message: String) {
  match level {
    LogLevel::Trace => trace!("zzz{}", message),
    LogLevel::Debug => debug!("zzz{}", message),
    LogLevel::Info => info!("zzz{}", message),
    LogLevel::Warn => warn!("zzz{}", message),
    LogLevel::Error => error!("zzz{}", message),
  }
}

pub enum LogTarget {
  Stdout,
  Stderr,
  Folder(PathBuf),
}

/// The logger.
pub struct LoggerBuilder {
  level: LevelFilter,
  rotation_strategy: RotationStrategy,
  targets: Vec<LogTarget>,
}

impl LoggerBuilder {
  pub fn new<T: IntoIterator<Item = LogTarget>>(targets: T) -> Self {
    let mut t = Vec::new();
    for target in targets {
      t.push(target);
    }
    let builder = Self {
      level: LevelFilter::Trace,
      targets: t,
      rotation_strategy: RotationStrategy::KeepOne,
    };
    builder
  }

  pub fn level(mut self, level: LevelFilter) -> Self {
    self.level = level;
    self
  }

  pub fn rotation_strategy(mut self, rotation_strategy: RotationStrategy) -> Self {
    self.rotation_strategy = rotation_strategy;
    self
  }

  pub fn build<R: Runtime>(self) -> Logger<R> {
    Logger {
      level: self.level,
      rotation_strategy: self.rotation_strategy,
      targets: self.targets,
      invoke_handler: Box::new(tauri::generate_handler![hello]),
    }
  }
}

pub struct Logger<R: Runtime> {
  level: LevelFilter,
  rotation_strategy: RotationStrategy,
  targets: Vec<LogTarget>,
  invoke_handler: Box<dyn Fn(Invoke<R>) + Send + Sync>,
}

pub async fn character_read() -> String {
  let mut instances = db_instances().lock().await;
  let pool = instances.get_mut(DB_URL).unwrap();

  // Read
  let (sql, values) = Query::select()
    .columns(vec![
      Character::Id,
      Character::Uuid,
      Character::Character,
      Character::FontSize,
      Character::Meta,
      Character::Created,
    ])
    .from(Character::Table)
    .order_by(Character::Id, Order::Desc)
    .limit(1)
    .build(SqliteQueryBuilder);

  let rows = bind_query_as(sqlx::query_as::<_, CharacterStruct>(&sql), &values)
    .fetch_all(&*pool)
    .await
    .unwrap();
  info!("Select one from character:");
  for row in rows.iter() {
    info!("read() {:?}", row);
  }
  info!("Done read() {}", "--");

  "".into()
}


pub async fn character_create() -> i64 {
  let mut instances = db_instances().lock().await;
  let pool = instances.get_mut(DB_URL).unwrap();

  // Create
  let (sql, values) = Query::insert()
    .into_table(Character::Table)
    .columns(vec![
      Character::Uuid,
      Character::FontSize,
      Character::Character,
      Character::Meta,
      Character::Created,
    ])
    .values_panic(vec![
      Uuid::new_v4().into(),
      12.into(),
      "A".into(),
      json!({
          "notes": "some notes here",
      })
      .into(),
      NaiveDate::from_ymd(2020, 8, 20).and_hms(0, 0, 0).into(),
    ])
    .build(SqliteQueryBuilder);

  //TODO: Implement RETURNING (returning_col) for the Sqlite driver.
  let row = bind_query(sqlx::query(&sql), &values)
    .execute(&*pool)
    .await
    .unwrap();

  let id: i64 = row.last_insert_rowid();
  info!("Insert into character: last_insert_id = {}\n", id);

  id
}

pub async fn character_update(id: i64) {
  let mut instances = db_instances().lock().await;
  let pool = instances.get_mut(DB_URL).unwrap();

  // Update
  let (sql, values) = Query::update()
    .table(Character::Table)
    .values(vec![(Character::FontSize, 24.into())])
    .and_where(Expr::col(Character::Id).eq(id))
    .build(SqliteQueryBuilder);

  let result = bind_query(sqlx::query(&sql), &values).execute(&*pool).await;
  info!("Update character: {:?}\n", result);
}

pub async fn character_count() {
  let mut instances = db_instances().lock().await;
  let pool = instances.get_mut(DB_URL).unwrap();

  // Count
  let (sql, values) = Query::select()
    .from(Character::Table)
    .expr(Func::count(Expr::col(Character::Id)))
    .build(SqliteQueryBuilder);

  let row = bind_query(sqlx::query(&sql), &values)
    .fetch_one(&*pool)
    .await
    .unwrap();

  let count: i64 = row.try_get(0).unwrap();
  info!("Count character: {}\n", count);
}

pub async fn character_delete(id: i64) {
  let mut instances = db_instances().lock().await;
  let pool = instances.get_mut(DB_URL).unwrap();

  // Delete
  let (sql, values) = Query::delete()
    .from_table(Character::Table)
    .and_where(Expr::col(Character::Id).eq(id))
    .build(SqliteQueryBuilder);

  let result = bind_query(sqlx::query(&sql), &values).execute(&*pool).await;

  info!("Delete character: {:?}", result);
}

fn character_schema()->String{
  Table::create()
      .table(Character::Table)
      .if_not_exists()
      .col(
        ColumnDef::new(Character::Id)
            .integer()
            .not_null()
            .auto_increment()
            .primary_key(),
      )
      .col(ColumnDef::new(Character::Uuid).uuid())
      .col(ColumnDef::new(Character::FontSize).integer())
      .col(ColumnDef::new(Character::Character).string())
      .col(ColumnDef::new(Character::Meta).json())
      .col(ColumnDef::new(Character::Created).date_time())
      .build(SqliteQueryBuilder)

}



impl<R: Runtime> Plugin<R> for Logger<R> {
  fn name(&self) -> &'static str {
    "sqlite"
  }

  fn initialize(&mut self, _app: &AppHandle<R>, config: JsonValue) -> PluginResult<()> {
    tauri::async_runtime::block_on(async move {
      let config: LogConfiguration = if config.is_null() {
        Default::default()
      } else {
        serde_json::from_value(config)?
      };

      let mut instances = db_instances().lock().await;

      if !Db::database_exists(DB_URL).await.unwrap_or(false) {
        Db::create_database(DB_URL).await?;
      }

      let pool = Pool::connect(DB_URL).await.unwrap();

      // Schema
      let sql = character_schema();
      let result = sqlx::query(&sql).execute(&pool).await;
      info!("Create table character: {:?}\n", result);

      instances.insert(DB_URL.into(), pool);

      Ok(())
    })
  }

  fn extend_api(&mut self, message: Invoke<R>) {
    (self.invoke_handler)(message)
  }
}

#[derive(Iden)]
enum Character {
  Table,
  Id,
  Uuid,
  Character,
  FontSize,
  Meta,
  Created,
}

#[derive(sqlx::FromRow, Debug)]
struct CharacterStruct {
  id: i32,
  uuid: Uuid,
  character: String,
  font_size: i32,
  meta: JsonValue,
  created: NaiveDateTime,
}
